import Axios, {
  AxiosRequestConfig,
  CancelTokenStatic,
  AxiosInstance,
} from 'axios'

import NProgress from '../progress'

import { genConfig } from './config'

import { transformConfigByMethod } from './utils'

import {
  cancelTokenType,
  RequestMethods,
  EnclosureHttpRequestConfig,
  EnclosureHttpResoponse,
  EnclosureHttpError,
} from './types'

class EnclosureHttp {
  constructor() {
    this.httpInterceptorsRequest()
    this.httpInterceptorsResponse()
  }
  // 初始化配置对象
  private static initConfig: EnclosureHttpRequestConfig = {}

  // 保存当前Axios实例对象
  private static axiosInstance: AxiosInstance = Axios.create(genConfig())

  // 保存 EnclosureHttp实例
  private static EnclosureHttpInstance: EnclosureHttp

  // axios取消对象
  private CancelToken: CancelTokenStatic = Axios.CancelToken

  // 取消的凭证数组
  private sourceTokenList: Array<cancelTokenType> = []

  // 记录当前这一次cancelToken的key
  private currentCancelTokenKey = ''

  // 当前调用目录前缀
  private urlPrefix = process.env.VUE_APP_API_URL

  private beforeRequestCallback: EnclosureHttpRequestConfig['beforeRequestCallback'] =
    undefined

  private beforeResponseCallback: EnclosureHttpRequestConfig['beforeResponseCallback'] =
    undefined

  public get cancelTokenList(): Array<cancelTokenType> {
    return this.sourceTokenList
  }

  // eslint-disable-next-line class-methods-use-this
  public set cancelTokenList(value) {
    throw new Error('cancelTokenList不允许赋值')
  }

  /**
   * @description 私有构造不允许实例化
   * @returns void 0
   */
  // constructor() {}

  /**
   * @description 生成唯一取消key
   * @param config axios配置
   * @returns string
   */
  // eslint-disable-next-line class-methods-use-this
  private genUniqueKey(config: EnclosureHttpRequestConfig): string {
    return `${config.url}--${JSON.stringify(config.data)}`
  }

  /**
   * @description 取消重复请求
   * @returns void 0
   */
  private cancelRepeatRequest(): void {
    const temp: { [key: string]: boolean } = {}

    this.sourceTokenList = this.sourceTokenList.reduce<Array<cancelTokenType>>(
      (res: Array<cancelTokenType>, cancelToken: cancelTokenType) => {
        const { cancelKey, cancelExecutor } = cancelToken
        if (!temp[cancelKey]) {
          temp[cancelKey] = true
          res.push(cancelToken)
        }
        else {
          cancelExecutor()
        }
        return res
      },
      []
    )
  }

  /**
   * @description 删除指定的CancelToken
   * @returns void 0
   */
  private deleteCancelTokenByCancelKey(cancelKey: string): void {
    this.sourceTokenList =
      this.sourceTokenList.length < 1
        ? this.sourceTokenList.filter(
          (cancelToken) => cancelToken.cancelKey !== cancelKey
        )
        : []
  }

  /**
   * @description 拦截请求
   * @returns void 0
   */

  private httpInterceptorsRequest(): void {
    EnclosureHttp.axiosInstance.interceptors.request.use(
      (config: EnclosureHttpRequestConfig) => {
        const $config = config
        NProgress.start() // 每次切换页面时，调用进度条
        const cancelKey = this.genUniqueKey($config)
        $config.cancelToken = new this.CancelToken(
          (cancelExecutor: (cancel: any) => void) => {
            this.sourceTokenList.push({ cancelKey, cancelExecutor })
          }
        )
        this.cancelRepeatRequest()
        this.currentCancelTokenKey = cancelKey
        // 优先判断post/get等方法是否传入回掉，否则执行初始化设置等回掉
        if (typeof this.beforeRequestCallback === 'function') {
          this.beforeRequestCallback($config)
          this.beforeRequestCallback = undefined
          return $config
        }
        if (EnclosureHttp.initConfig.beforeRequestCallback) {
          EnclosureHttp.initConfig.beforeRequestCallback($config)
          return $config
        }
        return $config
      },
      (error) => {
        return Promise.reject(error)
      }
    )
  }

  /**
   * @description 清空当前cancelTokenList
   * @returns void 0
   */
  public clearCancelTokenList(): void {
    this.sourceTokenList.length = 0
  }

  /**
   * @description 拦截相应
   * @returns void 0
   */
  private httpInterceptorsResponse(): void {
    const instance = EnclosureHttp.axiosInstance
    instance.interceptors.response.use(
      (response: EnclosureHttpResoponse) => {
        // 请求每次成功一次就删除当前canceltoken标记
        const cancelKey = this.genUniqueKey(response.config)
        this.deleteCancelTokenByCancelKey(cancelKey)
        // 优先判断post/get等方法是否传入回掉，否则执行初始化设置等回掉
        if (typeof this.beforeResponseCallback === 'function') {
          this.beforeResponseCallback(response)
          this.beforeResponseCallback = undefined
          return response.data
        }
        if (EnclosureHttp.initConfig.beforeResponseCallback) {
          EnclosureHttp.initConfig.beforeResponseCallback(response)
          return response.data
        }
        NProgress.done()
        return response.data
      },
      (error: EnclosureHttpError) => {
        const $error = error
        // 判断当前的请求中是否在 取消token数组理存在，如果存在则移除（单次请求流程）
        if (this.currentCancelTokenKey) {
          const haskey = this.sourceTokenList.filter(
            (cancelToken) =>
              cancelToken.cancelKey === this.currentCancelTokenKey
          ).length
          if (haskey) {
            this.sourceTokenList = this.sourceTokenList.filter(
              (cancelToken) =>
                cancelToken.cancelKey !== this.currentCancelTokenKey
            )
            this.currentCancelTokenKey = ''
          }
        }
        $error.isCancelRequest = Axios.isCancel($error)
        // 所有的响应异常 区分来源为取消请求/非取消请求
        return Promise.reject($error)
      }
    )
  }

  public request<T>(
    method: RequestMethods,
    url: string,
    param?: AxiosRequestConfig,
    axiosConfig?: EnclosureHttpRequestConfig
  ): Promise<T> {
    url = this.urlPrefix + url
    const config = transformConfigByMethod(param, {
      method,
      url,
      ...axiosConfig,
    } as EnclosureHttpRequestConfig)
    // 单独处理自定义请求/响应回掉
    if (axiosConfig?.beforeRequestCallback) {
      this.beforeRequestCallback = axiosConfig.beforeRequestCallback
    }
    if (axiosConfig?.beforeResponseCallback) {
      this.beforeResponseCallback = axiosConfig.beforeResponseCallback
    }
    return new Promise((resolve, reject) => {
      EnclosureHttp.axiosInstance
        .request(config)
        .then((response: any) => {
          // EnclosureHttpResoponse
          resolve(response)
        })
        .catch((error: any) => {
          reject(error)
        })
    })
  }

  public post<T>(
    url: string,
    params?: T,
    config?: EnclosureHttpRequestConfig
  ): Promise<T> {
    return this.request<T>('post', url, params, config)
  }

  public get<T>(
    url: string,
    params?: T,
    config?: EnclosureHttpRequestConfig
  ): Promise<T> {
    return this.request<T>('get', url, params, config)
  }
}

export default EnclosureHttp
